{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению\n",
    "<center>Автор материала: Павел Прохоров (@pavelvpster)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# SVM (Support Vector Machine)\n",
    "\n",
    "## Совсем немного теории\n",
    "\n",
    "SVM (Support Vector Machine, Машина Опорных Векторов) относится к классу алгоритмов обучения с учителем. Принадлежит семейству линейных алгоритмов классификации с максимальным зазором.\n",
    "\n",
    "Вспомним задачу бинарной классификации...\n",
    "\n",
    "### Жесткий зазор (hard-margin)\n",
    "\n",
    "Если данные являются линейно разделимыми, можно построить такую разделяющую гиперплоскость, которая максимизирует расстояние между двумя параллельными гиперплоскостями (изображение взято из статьи https://en.wikipedia.org/wiki/Support_vector_machine):\n",
    "\n",
    "<img src=\"../../img/svm_max_sep_hyperplane_with_margin.png\" alt=\"Разделяющая гиперплоскость\" style=\"width: 300px;\"/>\n",
    "\n",
    "Каждая точка данных должна находится на \"правильной\" стороне от разделяющей гиперплоскости.\n",
    "\n",
    "Это условие можно записать так:\n",
    "\n",
    "$$ y_{i} (w^T x_i - b) \\geqslant 1, i = 1..n $$\n",
    "\n",
    "где\n",
    "\n",
    "$ y $ - метки целевого класса +1 или -1\n",
    "\n",
    "$ x $ - признаки (каждый признак это вектор)\n",
    "\n",
    "$ w $ - коэффициенты модели (каждый коэффициент тоже вектор)\n",
    "\n",
    "Тогда задача построения разделяющей гиперплоскости формулируется так:\n",
    "\n",
    "$$ \\begin{cases} \\frac{1}{2} w^T w \\to min \\\\ y_{i} (w^T x_i - b) \\geqslant 1, i = 1..n \\end{cases} $$\n",
    "\n",
    "Ближайшие к гиперплоскостям точки называются опорными векторами. Их достаточно для построения модели, все точки не нужны.\n",
    "\n",
    "### Мягкий зазор (soft-margin)\n",
    "\n",
    "Если данные не являются линейно разделимыми, часть точек окажется на \"неправильной\" стороне.\n",
    "\n",
    "Минимизировать надо функцию потерь. В SVM используется петлевая функция потерь [hinge loss](https://en.wikipedia.org/wiki/Hinge_loss):\n",
    "\n",
    "$$ \\zeta_i = max(0, 1 - y_{i} (w^T x_i - b)) $$\n",
    "\n",
    "Значение функции потерь равно 0, если точка находится на правильной стороне разделяющей гиперплоскости и пропорционально расстоянию до разделяющей гиперплоскости, если точка находится на неправильной стороне.\n",
    "\n",
    "На следующем графике приведены zero-one-loss, hinge loss, squared hinge loss:\n",
    "\n",
    "<img src=\"../../img/svm_loss_functions.png\" alt=\"Функции потерь\" style=\"width: 400px;\"/>\n",
    "\n",
    "Squared hinge loss используется потому, что производная от hinge loss в точке $ y_{i} (w^T x_i - b) = 1 $ не определена.\n",
    "\n",
    "Задача построения разделяющей гиперплоскости:\n",
    "\n",
    "$$ \\begin{cases} \\frac{1}{2} w^T w + C \\sum_{i=1}^n \\zeta_i \\to min \\\\ y_{i} (w^T x_i - b) \\geqslant 1 - \\zeta_i , \\zeta_i \\geqslant 0, i = 1..n \\end{cases} $$\n",
    "\n",
    "где $ C $ определяет соотношение между целями максимизации зазора и уменьшения ошибки.\n",
    "\n",
    "### Нелинейный случай\n",
    "\n",
    "Если данные не являются линейно разделимыми, можно попробовать выбрать пространство более высокой размерности в котором они будут линейно разделимыми.\n",
    "\n",
    "Это означает, что в формулах $ x_i $ надо заменить на функцию $ \\phi(x_i) $:\n",
    "\n",
    "$$ \\begin{cases} \\frac{1}{2} w^T w + C \\sum_{i=1}^n \\zeta_i \\to min \\\\ y_{i} (w^T \\phi(x_i) - b) \\geqslant 1 - \\zeta_i , \\zeta_i \\geqslant 0, i = 1..n \\end{cases} $$\n",
    "\n",
    "Однако, вычислять значение $ \\phi(x_i) $ не потребуется.\n",
    "\n",
    "Запишем вектор коэффициентов $ w $ как линейную комбинацию опорных векторов:\n",
    "\n",
    "$$ w = \\sum_{i=0}^l y_i \\alpha_i \\phi(x_i) $$\n",
    "\n",
    "Тогда произведение $ w^T x $ примет вид:\n",
    "\n",
    "$$ w^T x = \\sum_{i=0}^l y_i \\alpha_i \\phi(x_i) \\cdot \\phi(x) = \\sum_{i=0}^l y_i \\alpha_i K(x_i, x) $$\n",
    "\n",
    "где $K$ - функция ядра.\n",
    "\n",
    "В библиотеке scikit-learn предлагаются следуюшие функции ядра:\n",
    "\n",
    "- линейную (linear): $ K(x,x') = \\langle x, x' \\rangle $\n",
    "- полиномиальную (polynomial): $ K(x,x') = (\\gamma \\langle x, x' \\rangle + r)^d $\n",
    "- RBF: $ K(x,x') = e^{-\\gamma || x - x' ||^2} $\n",
    "- сигмоид (sigmoid): $ K(x,x') = tanh(\\gamma \\langle x, x' \\rangle + r) $\n",
    "\n",
    "Вот такой вот [kernel method](https://en.wikipedia.org/wiki/Kernel_method)\n",
    "\n",
    "Но хватит, теории - займемся практикой..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "warnings.filterwarnings('ignore')\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from matplotlib import pyplot as plt\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.metrics import accuracy_score\n",
    "from sklearn.svm import SVC, LinearSVC"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Простой пример"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для начала классы будут линейно разделимыми.\n",
    "\n",
    "Подготовим данные: два облака точек (красные и синие) с нормальным распределением координат."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = 50\n",
    "B = 200\n",
    "\n",
    "X_1 = pd.DataFrame({'x': np.concatenate([np.random.normal(2, 0.5, A), np.random.normal(5, 0.5, B)]),\n",
    "                    'y': np.concatenate([np.random.normal(2, 0.5, A), np.random.normal(5, 0.5, B)])})\n",
    "\n",
    "X_1.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y_1 = pd.DataFrame({'target': np.concatenate([np.repeat(0, A), np.repeat(1, B)])})\n",
    "\n",
    "Y_1['target'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(X_1.x, X_1.y, c=['red' if y == 0 else 'blue' if y == 1 else 'black' for y in Y_1.target])\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Полезные функции для отображения границ"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Код функций `make_meshgrid` и `plot_contours` заимствован из [примера к библиотеке scikit-learn](http://scikit-learn.org/stable/auto_examples/svm/plot_iris.html#sphx-glr-auto-examples-svm-plot-iris-py)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def make_meshgrid(x, y, h=.02):\n",
    "    x_min, x_max = x.min() - 1, x.max() + 1\n",
    "    y_min, y_max = y.min() - 1, y.max() + 1\n",
    "    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))\n",
    "    return xx, yy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_contours(ax, classifier, xx, yy, **params):\n",
    "    Z = classifier.predict(np.c_[xx.ravel(), yy.ravel()])\n",
    "    Z = Z.reshape(xx.shape)\n",
    "    out = ax.contourf(xx, yy, Z, **params)\n",
    "    return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compare_and_plot(X, Y):\n",
    "    \n",
    "    classifiers = [LogisticRegression(), LinearSVC(), SVC(kernel='poly'), SVC(kernel='rbf')]\n",
    "    \n",
    "    for classifier in classifiers:\n",
    "        classifier.fit(X, Y)\n",
    "    \n",
    "    classifier_names = ['Logistic Regression', 'SVM (Linear Kernel)', 'SVM (Polynomial Kernel)', 'SVM (RBF Kernel)']\n",
    "    \n",
    "    color = ['red' if y == 0 else 'blue' if y == 1 else 'black' for y in Y.target]\n",
    "    \n",
    "    xx, yy = make_meshgrid(X.x, X.y)\n",
    "    \n",
    "    fig, sub = plt.subplots(2, 2, figsize=(15,15))\n",
    "    \n",
    "    plt.subplots_adjust(wspace=0.2, hspace=0.2)\n",
    "    \n",
    "    for name, classifier, ax in zip(classifier_names, classifiers, sub.flatten()):\n",
    "        \n",
    "        plot_contours(ax, classifier, xx, yy, cmap='summer', alpha=0.25)\n",
    "        \n",
    "        ax.scatter(X.x, X.y, c=color)\n",
    "        \n",
    "        predictions = classifier.predict(X)\n",
    "        acc = accuracy_score(Y, predictions)\n",
    "        \n",
    "        ax.set_title('{}. Accuracy={}'.format(name, acc))\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Построим границы классификации"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "compare_and_plot(X_1, Y_1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Какие выводы можно сделать по этим графикам?\n",
    "\n",
    "1. Логистическая регрессия оценивает вероятность принадлежности к классу. Поэтому граница дальше от облака синих точек: их больше, значит для какой-либо точки (новой) вероятность быть отнесенной к синим точкам больше.\n",
    "\n",
    "2. SVM с линейным и полиномиальным ядрами проводит границу на одинаковом расстоянии между классами.\n",
    "\n",
    "3. SVM с RBF ядром формирует границу сложной формы (понадобится для случая, когда классы не являются линейно разделимыми).\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Пример посложнее"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Теперь рассмотрим случай, когда классы не являются линейно разделимыми."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_2 = pd.DataFrame({'x': np.concatenate([np.random.normal(2, 1.5, A), np.random.normal(5, 1.5, B)]),\n",
    "                    'y': np.concatenate([np.random.normal(2, 1.5, A), np.random.normal(5, 1.5, B)])})\n",
    "\n",
    "X_2.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y_2 = pd.DataFrame({'target': np.concatenate([np.repeat(0, A), np.repeat(1, B)])})\n",
    "\n",
    "Y_2['target'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(X_2.x, X_2.y, c=['red' if y == 0 else 'blue' if y == 1 else 'black' for y in Y_2.target])\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "compare_and_plot(X_2, Y_2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Выводы\n",
    "\n",
    "1. Граница у SVM с полиномиальным ядром стала сложнее.\n",
    "\n",
    "2. Масимальную точность показала SVM с ядром RBF.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Совсем сложный пример"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_3 = pd.DataFrame({'x': np.random.normal(5, 2.0, 250),\n",
    "                    'y': np.random.normal(5, 2.0, 250)})\n",
    "\n",
    "X_3.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Y_3 = pd.DataFrame(index=X_3.index)\n",
    "\n",
    "Y_3['target'] = X_3.apply(lambda row: np.linalg.norm(row - [5,5]) < 2, axis=1)\n",
    "\n",
    "Y_3['target'].value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.scatter(X_3.x, X_3.y, c=['red' if y == 0 else 'blue' if y == 1 else 'black' for y in Y_3.target])\n",
    "\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "compare_and_plot(X_3, Y_3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Выводы\n",
    "\n",
    "1. Логистическая регрессия и SVM с линейным ядром все точки отнесли к одному классу.\n",
    "\n",
    "2. У SVM с полиномиальным ядром результат лучше.\n",
    "\n",
    "3. У SVM с ядром RBF точность максимальна.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Классы библиотеки scikit-learn\n",
    "\n",
    "Библиотека предлагает следующие реализации алгоритма SVM:\n",
    "\n",
    "[LinearSVC](http://scikit-learn.org/stable/modules/generated/sklearn.svm.LinearSVC.html) - реализация SVM с линейным ядром для задач классификации\n",
    "\n",
    "[LinearSVR](http://scikit-learn.org/stable/modules/generated/sklearn.svm.LinearSVR.html) - реализация SVM с линейным ядром для задач регрессии\n",
    "\n",
    "[SVC](http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html) - наиболее общая реализация SVM для задач классификации\n",
    "\n",
    "[SVR](http://scikit-learn.org/stable/modules/generated/sklearn.svm.SVR.html) - наиболее общая реализация SVM для задач регрессии\n",
    "\n",
    "[NuSVC](http://scikit-learn.org/stable/modules/generated/sklearn.svm.NuSVC.html) - реализация SVM для задач классификации с возможностью задать количество опорных векторов\n",
    "\n",
    "[NuSVR](http://scikit-learn.org/stable/modules/generated/sklearn.svm.NuSVR.html) - реализация SVM для задач регрессии с возможностью задать количество опорных векторов\n",
    "\n",
    "[OneClassSVM](http://scikit-learn.org/stable/modules/generated/sklearn.svm.OneClassSVM.html) - реализация SVM для задачи обнаружения отклонений без учителя\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Реальный пример\n",
    "\n",
    "Попробуем сравнить SVM с линейным ядром и с ядром RBF на реальном примере: [соревновании Титиник](https://www.kaggle.com/c/titanic)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import StratifiedKFold, cross_val_score\n",
    "from sklearn.preprocessing import LabelEncoder, StandardScaler"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Считаем данные"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X = pd.read_csv(\"../../data/titanic_train.csv\")\n",
    "\n",
    "y = X['Survived']\n",
    "\n",
    "Z = pd.read_csv(\"../../data/titanic_test.csv\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Заполним пропуски"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X['Age'].fillna(X['Age'].median(), inplace=True)\n",
    "\n",
    "X['Embarked'].fillna('S', inplace=True)\n",
    "\n",
    "\n",
    "Z['Age'].fillna(Z['Age'].median(), inplace=True)\n",
    "\n",
    "Z['Fare'].fillna(Z['Fare'].median(), inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уберем лишние столбцы"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X.drop(['Survived', 'Name', 'Ticket', 'Cabin', 'PassengerId'], axis=1, inplace=True)\n",
    "\n",
    "Z.drop(['Name', 'Ticket', 'Cabin', 'PassengerId'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y.value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Z.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Закодируем признаки `Sex` и `Embarked` при помощи `LabelEncoder`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sex_label_encoder = LabelEncoder().fit(X['Sex'])\n",
    "\n",
    "X['Sex_num'] = sex_label_encoder.transform(X['Sex'])\n",
    "\n",
    "X.drop(['Sex'], axis=1, inplace=True)\n",
    "\n",
    "Z['Sex_num'] = sex_label_encoder.transform(Z['Sex'])\n",
    "\n",
    "Z.drop(['Sex'], axis=1, inplace=True)\n",
    "\n",
    "\n",
    "embarked_label_encoder = LabelEncoder().fit(X['Embarked'])\n",
    "\n",
    "X['Embarked_num'] = embarked_label_encoder.transform(X['Embarked'])\n",
    "\n",
    "X.drop(['Embarked'], axis=1, inplace=True)\n",
    "\n",
    "Z['Embarked_num'] = embarked_label_encoder.transform(Z['Embarked'])\n",
    "\n",
    "Z.drop(['Embarked'], axis=1, inplace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признаки надо отмасштабировать"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaler = StandardScaler().fit(X)\n",
    "\n",
    "X_scaled = scaler.transform(X)\n",
    "\n",
    "Z_scaled = scaler.transform(Z)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для сравнения моделей будем использовать кросс-валидацию"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И наконец, сравним модели на кросс-валидации"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "logit = LogisticRegression().fit(X_scaled, y)\n",
    "\n",
    "cross_val_score(logit, X_scaled, y, cv=skf).mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "linear_svc = LinearSVC().fit(X_scaled, y)\n",
    "\n",
    "cross_val_score(linear_svc, X_scaled, y, cv=skf).mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "svm_rbf = SVC(kernel='rbf', class_weight='balanced').fit(X_scaled, y)\n",
    "\n",
    "cross_val_score(svm_rbf, X_scaled, y, cv=skf).mean()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "SVM с ядром RBF показала наилучшие результаты"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Сделаем посылку чтобы проверить наше предположение"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def write_to_submission_file(predicted_labels, out_file, train_num=891,\n",
    "                             target='Survived', index_label=\"PassengerId\"):\n",
    "    \n",
    "    predicted_df = pd.DataFrame(predicted_labels,\n",
    "                                index = np.arange(train_num + 1,\n",
    "                                                  train_num + 1 +\n",
    "                                                  predicted_labels.shape[0]),\n",
    "                                columns=[target])\n",
    "    \n",
    "    predicted_df.to_csv(out_file, index_label=index_label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "write_to_submission_file(logit.predict(Z_scaled), 'titanic_logit.csv')\n",
    "\n",
    "# Public Score = 0.75119"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "write_to_submission_file(linear_svc.predict(Z_scaled), 'titanic_linear_svc.csv')\n",
    "\n",
    "# Public Score = 0.75119"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "write_to_submission_file(svm_rbf.predict(Z_scaled), 'titanic_svm_rbf.csv')\n",
    "\n",
    "# Public Score = 0.77511"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Полезные ссылки\n",
    "\n",
    "http://scikit-learn.org/stable/modules/svm.html\n",
    "\n",
    "Сравнение SVM и логистический регрессии:\n",
    "http://www.cs.toronto.edu/~kswersky/wp-content/uploads/svm_vs_lr.pdf\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
